#include <iostream>
#include <cmath>
#include <QColor>
#include <QMessageBox>
#include "cimageproc.h"

using namespace std;

int gMasks[3][3][3] =
{
    {
        {0, -1, 0},
        {-1, 4, -1},
        {0, -1, 0}
    },
    {
        {-1, -1, -1},
        {-1, 8, -1},
        {-1, -1, -1}
    },
    {
        {1, -2, 1},
        {-2, 4, -2},
        {1, -2, 1}
    }
};

inline unsigned short ScaleColorTo255(unsigned short _val)
{
    return (_val * 255) / 65535;
}

float round( float fValue )
{
    return fValue < 0 ? ceil( fValue - 0.5 )
        : floor( fValue + 0.5 );
}

CImageProc::CImageProc()
{
    m_Brightness = 0;
    m_Contrast = 101;
    m_PrevData = NULL;
    m_Fourier = new CFourierTransform(512, 512);      // DEBUG!
    m_Segmentation = new Segmentation();
    m_Filter = NULL;
}

QImage *CImageProc::GetData() const
{
    return m_Data;
}

void CImageProc::SetData(QImage *Data)
{
    m_Data = Data;
}
Segmentation *CImageProc::GetSeg(){
    return m_Segmentation;
}
QImage *CImageProc::GetPreviousData()
{
    return m_PrevData;
}

void CImageProc::SetPreviousData()
{
    m_PrevData = new QImage(*m_Data);
}

QImage *CImageProc::Negative()
{
    uint col;

    SetPreviousData();

    for (int i = 0; i < m_Data->width(); i++)
        for (int j = 0; j < m_Data->height(); j++)
        {
            col = m_Data->pixel(i, j);
            col = 0xFFFFFFFF - col;
            m_Data->setPixel(i, j, col);
        }

    return m_Data;
}

QImage *CImageProc::Brightness(int _val)
{
    uint col;
    int red;
    int green;
    int blue;

    SetPreviousData();

    m_Brightness = _val;

    for (int i = 0; i < m_Data->width(); i++)
        for (int j = 0; j < m_Data->height(); j++)
        {
            col = m_Data->pixel(i, j);
            red = (col >> 16) & 0xFF;
            green = (col >> 8) & 0xFF;
            blue = col & 0xFF;

            red += m_Brightness;

            if (red > 255)
                red = 255;
            else if (red < 0)
                red = 0;

            green += m_Brightness;

            if (green > 255)
                green = 255;
            else if (green < 0)
                green = 0;

            blue += m_Brightness;

            if (blue > 255)
                blue = 255;
            else if (blue < 0)
                blue = 0;

            col = 0xFF;
            col <<= 8;
            col |= red;
            col <<= 8;
            col |= green;
            col <<= 8;
            col |= blue;

            m_Data->setPixel(i, j, col);
        }

    m_Brightness = _val;

    return m_Data;
}

QImage *CImageProc::Contrast(int _val)
{
    uint col;
    unsigned short red;
    unsigned short green;
    unsigned short blue;
    float contrastFactor;

    SetPreviousData();

    contrastFactor = (_val * 1.0f)/ 100.0f;

    for (int i = 0; i < m_Data->width(); i++)
        for (int j = 0; j < m_Data->height(); j++)
        {
            col = m_Data->pixel(i, j);
            red = (col >> 16) & 0xFF;
            green = (col >> 8) & 0xFF;
            blue = col & 0xFF;

            red *= contrastFactor;

            if (red > 255)
                red = 255;
            else if (red < 0)
                red = 0;

           green *= contrastFactor;

            if (green > 255)
                green = 255;
            else if (green < 0)
                green = 0;

            blue *= contrastFactor;

            if (blue > 255)
                blue = 255;
            else if (blue < 0)
                blue = 0;

            col = 0xFF;
            col <<= 8;
            col |= red;
            col <<= 8;
            col |= green;
            col <<= 8;
            col |= blue;

            m_Data->setPixel(i, j, col);
        }

    m_Contrast = _val;

    return m_Data;
}

/*
Błąd średniokwadratowy
Stosunek sygnału do szumu (signal to noise ratio) SNR
*/

QImage *CImageProc::ArithmeticFilter(const QImage& _orig)
{
    int bordersMin = GetWindowBordersRangeMin();
    int bordersMax = GetWindowBordersRangeMax();

    SetPreviousData();

    for (int i = 0; i < m_Data->width(); i++)
        for (int j = 0; j < m_Data->height(); j++)
        {
            /* Normal case (whole window is in the image)
             *
             * For instance (window 3x3):
             *
             * # # #
             * # # #
             * # # #
             *
             **/
            if  ( ( (i >= bordersMin) && (i <= bordersMax) ) &&
                ( (j >= bordersMin) && (j <= bordersMax) ) )
                {
                    CountArithmeticColor(_orig, i, j, -bordersMin, -bordersMin);
                }

            /* Particular case: (left column(s) cut off)
             *
             * For instance (window 3x3):
             *
             * # #
             * # #
             * # #
             *
             **/
            else if (( (i < bordersMin) && (i <= bordersMax) ) &&
                     ( (j >= bordersMin) && (j <= bordersMax) ) )
                {
                    CountArithmeticColor(_orig, i, j, 0, -bordersMin);
                }

            /* Particular case: (right column(s) cut off)
             *
             * For instance (window 3x3):
             *
             * # #
             * # #
             * # #
             *
             **/
            else if (( (i >= bordersMin) && (i > bordersMax) ) &&
                     ( (j >= bordersMin) && (j <= bordersMax) ) )
                {
                    CountArithmeticColor(_orig, i, j, -bordersMax, -bordersMin);
                }

            /* Particular case: (top row(s) cut off)
             *
             * For instance (window 3x3):
             *
             * # # #
             * # # #
             *
             **/
            else if (( (i >= bordersMin) && (i <= bordersMax) ) &&
                     ( (j < bordersMin) && (j <= bordersMax) ) )
                {
                    CountArithmeticColor(_orig, i, j, -bordersMin, 0);
                }

            /* Particular case: (bottom row(s) cut off)
             *
             * For instance (window 3x3):
             *
             * # # #
             * # # #
             *
             **/
            else if (( (i >= bordersMin) && (i <= bordersMax) ) &&
                     ( (j >= bordersMin) && (j > bordersMax) ) )
                {
                    CountArithmeticColor(_orig, i, j, -bordersMin, -bordersMax);
                }
        }

    QMessageBox msgBox;
    msgBox.setText("Odszumianie filtrem ze średnią arytmetyczną zakończone.");
    msgBox.setStandardButtons(QMessageBox::Ok);
    msgBox.exec();

    return m_Data;
}

int CImageProc::GetWindowBordersRangeMin()
{
    switch(m_FilterWindowSize)
    {
        case 3:
            return 1;
        break;

        case 5:
            return 2;
        break;

        case 7:
            return 3;
        break;
    }

    return -1;
}

int CImageProc::GetWindowBordersRangeMax()
{
    switch(m_FilterWindowSize)
    {
        case 3:
            return m_Data->width() - 2;         // Should be: width() == height()
        break;

        case 5:
            return m_Data->width() - 3;         // Should be: width() == height()
        break;

        case 7:
            return m_Data->width() - 4;         // Should be: width() == height()
        break;
    }

    return -1;
}

void CImageProc::CountArithmeticColor(const QImage& _orig, int _pixX, int _pixY, int _varK, int _varM)
{
    uint color;
    int red = 0;
    int green = 0;
    int blue = 0;

    for (int k = 0; k < m_FilterWindowSize; k++)
        for (int m = 0; m < m_FilterWindowSize; m++)
        {
            color = _orig.pixel(_pixX + (k + _varK), _pixY + (m + _varM));
            red += (color >> 16) & 0xFF;
            green += (color >> 8) & 0xFF;
            blue += color & 0xFF;
        }

    red /= m_FilterWindowSize * m_FilterWindowSize;
    green /= m_FilterWindowSize * m_FilterWindowSize;
    blue /= m_FilterWindowSize * m_FilterWindowSize;

    color = 0xFF;
    color <<= 8;
    color |= red;
    color <<= 8;
    color |= green;
    color <<= 8;
    color |= blue;

    m_Data->setPixel(_pixX, _pixY, color);
}

QImage *CImageProc::MedianFilter(const QImage& _orig)
{
    int bordersMin = GetWindowBordersRangeMin();
    int bordersMax = GetWindowBordersRangeMax();

    SetPreviousData();

    for (int i = 0; i < m_Data->width(); i++)
        for (int j = 0; j < m_Data->height(); j++)
        {
            /* Normal case (whole window is in the image)
             *
             * For instance (window 3x3):
             *
             * # # #
             * # # #
             * # # #
             *
             **/
            if  ( ( (i >= bordersMin) && (i <= bordersMax) ) &&
                ( (j >= bordersMin) && (j <= bordersMax) ) )
                {
                    CountMedianColor(_orig, i, j, -bordersMin, -bordersMin);
                }

            /* Particular case: (left column(s) cut off)
             *
             * For instance (window 3x3):
             *
             * # #
             * # #
             * # #
             *
             **/
 /*           else if (( (i < bordersMin) && (i <= bordersMax) ) &&
                     ( (j >= bordersMin) && (j <= bordersMax) ) )
                {
                    CountMedianColor(_orig, i, j, 0, -bordersMin);
                }

            /* Particular case: (right column(s) cut off)
             *
             * For instance (window 3x3):
             *
             * # #
             * # #
             * # #
             *
             **/
       /*     else if (( (i >= bordersMin) && (i > bordersMax) ) &&
                     ( (j >= bordersMin) && (j <= bordersMax) ) )
                {
                    CountMedianColor(_orig, i, j, -bordersMax, -bordersMin);
                }

            /* Particular case: (top row(s) cut off)
             *
             * For instance (window 3x3):
             *
             * # # #
             * # # #
             *
             **/
     /*       else if (( (i >= bordersMin) && (i <= bordersMax) ) &&
                     ( (j < bordersMin) && (j <= bordersMax) ) )
                {
                    CountMedianColor(_orig, i, j, -bordersMin, 0);
                }

            /* Particular case: (bottom row(s) cut off)
             *
             * For instance (window 3x3):
             *
             * # # #
             * # # #
             *
             **/
       /*     else if (( (i >= bordersMin) && (i <= bordersMax) ) &&
                     ( (j >= bordersMin) && (j > bordersMax) ) )
                {
                    CountMedianColor(_orig, i, j, -bordersMin, -bordersMax);
                }*/
        }

    QMessageBox msgBox;
    msgBox.setText("Odszumianie filtrem medianowym zakończone.");
    msgBox.setStandardButtons(QMessageBox::Ok);
    msgBox.exec();

    return m_Data;
}

void CImageProc::CountMedianColor(const QImage& _orig, int _pixX, int _pixY, int _varK, int _varM)
{
    uint color;
    vector<uint> colList;

    for (int k = 0; k < m_FilterWindowSize; k++)
        for (int m = 0; m < m_FilterWindowSize; m++)
        {
            color = _orig.pixel(_pixX + (k + _varK), _pixY + (m + _varM));
            colList.push_back(color);
        }

    std::sort(colList.begin(), colList.end());
    color = colList[ (colList.size() + 1) / 2];
    m_Data->setPixel(_pixX, _pixY, color);
}

QImage *CImageProc::Histogram(const QImage& _orig, short _type)     // _type: 0 - RED, 1 - GREEN, 2 - BLUE
{
    uint color;
    int maxR = 0;
    int maxG = 0;
    int maxB = 0;
    QImage *retImg = new QImage(256, 256, m_Data->format());
    int scaleY = 0;
    uint histColor;

    // Initialize histograms (of values: 0)
    InitializeHistogram(m_HistogramR);
    InitializeHistogram(m_HistogramG);
    InitializeHistogram(m_HistogramB);

    if (_type == 0)
    {
        histColor = 0xFFFF0000;

        // Make red channel histogram
        for (int i = 0; i < _orig.width(); i++)
            for (int j = 0; j < _orig.height(); j++)
            {
                color = _orig.pixel(i, j);
                color = (color >> 16) & 0xFF;           // Red component
                m_HistogramR[color]++;

                if (maxR < m_HistogramR[color])
                    maxR = m_HistogramR[color];
            }

        scaleY = round((float) (maxR * (1.0f) / 200.0f));

        if (scaleY == 0)
            scaleY = 1;

        for (uint i = 0; i < m_HistogramR.size(); i++)
        {
            if (m_HistogramR[i] == 0)
                continue;

            int idx = 0;
            for (int j = 0; j < m_HistogramR[i]; j += scaleY, idx++)
                retImg->setPixel(i, retImg->height() - 1 - idx, histColor);
        }
    }

    if (_type == 1)
    {
        histColor = 0xFF00FF00;

        // Make green channel histogram
        for (int i = 0; i < _orig.width(); i++)
            for (int j = 0; j < _orig.height(); j++)
            {
                color = _orig.pixel(i, j);
                color = (color >> 8) & 0xFF;           // Green component
                m_HistogramG[color]++;

                if (maxG < m_HistogramG[color])
                    maxG = m_HistogramG[color];
            }

        scaleY = round((float) (maxG * (1.0f) / 200.0f));

        if (scaleY == 0)
            scaleY = 1;

        for (uint i = 0; i < m_HistogramG.size(); i++)
        {
            if (m_HistogramG[i] == 0)
                continue;

            int idx = 0;
            for (int j = 0; j < m_HistogramG[i]; j += scaleY, idx++)
                retImg->setPixel(i, retImg->height() - 1 - idx, histColor);
        }
    }

    if (_type == 2)
    {
        histColor = 0xFF0000FF;

        // Make blue channel histogram
        for (int i = 0; i < _orig.width(); i++)
            for (int j = 0; j < _orig.height(); j++)
            {
                color = _orig.pixel(i, j);
                color = color & 0xFF;                   // Blue component
                m_HistogramB[color]++;

                if (maxB < m_HistogramB[color])
                    maxB = m_HistogramB[color];
            }

        scaleY = round((float) (maxB * (1.0f) / 200.0f));

        if (scaleY == 0)
            scaleY = 1;

        for (uint i = 0; i < m_HistogramB.size(); i++)
        {
            if (m_HistogramB[i] == 0)
                continue;

            int idx = 0;
            for (int j = 0; j < m_HistogramB[i]; j += scaleY, idx++)
                retImg->setPixel(i, retImg->height() - 1 - idx, histColor);
        }
    }


    return retImg;
}

void CImageProc::InitializeHistogram(map<uint, int>& _hist)
{
    for (uint i = 0; i < 256; i++)
        _hist[i, 0];
}

QImage *CImageProc::ModifyImgByHistogram(const QImage& _img, short _type, int _gMin)
{
    QImage *retImg = new QImage(256, 256, m_Data->format());
    std::map<uint, int>  newHistogramR;
    std::map<uint, int>  newHistogramG;
    std::map<uint, int>  newHistogramB;
    int maxR = 0;
    int maxG = 0;
    int maxB = 0;
    uint histColor;
    int scaleY = 0;

    // H2:
    int alpha = 1;          // TRY!
    int N = _img.width() * _img.height();
    int sum = 0;
    int ln = 0;

    if (_type == 0)
    {
        histColor = 0xFFFF0000;

        // Make red channel histogram
        for (int i = 0; i < m_HistogramR.size(); i++)
        {
            for (int j = 0; j < i; j++)
                sum += m_HistogramR[j];

            ln = round( 1.0f - ( (sum * 1.0f) / (N * 1.0f) ) );

            newHistogramR[i] = _gMin - round(ln / (alpha * 1.0f));

            if (maxR < newHistogramR[i])
                maxR = newHistogramR[i];
        }

        scaleY = round((float) (maxR * (1.0f) / 200.0f));

        if (scaleY == 0)
            scaleY = 1;

        for (uint i = 0; i < newHistogramR.size(); i++)
        {
            if (newHistogramR[i] == 0)
                continue;

            int idx = 0;
            for (int j = 0; j < newHistogramR[i]; j += scaleY, idx++)
                retImg->setPixel(i, retImg->height() - 1 - idx, histColor);
        }
    }
    else if (_type == 1)
    {
        histColor = 0xFF00FF00;

        // Make green channel histogram
        for (int i = 0; i < m_HistogramG.size(); i++)
        {
            for (int j = 0; j < i; j++)
                sum += m_HistogramG[j];

            ln = round( 1.0f - ( (sum * 1.0f) / (N * 1.0f) ) );

            newHistogramG[i] = _gMin - round(ln / (alpha * 1.0f));

            if (maxG < newHistogramG[i])
                maxG = newHistogramG[i];
        }

        scaleY = round((float) (maxG * (1.0f) / 200.0f));

        if (scaleY == 0)
            scaleY = 1;

        for (uint i = 0; i < newHistogramG.size(); i++)
        {
            if (newHistogramG[i] == 0)
                continue;

            int idx = 0;
            for (int j = 0; j < newHistogramG[i]; j += scaleY, idx++)
                retImg->setPixel(i, retImg->height() - 1 - idx, histColor);
        }
    }
    else if (_type == 2)
    {
        histColor = 0xFF0000FF;

        // Make blue channel histogram
        for (int i = 0; i < m_HistogramB.size(); i++)
        {
            for (int j = 0; j < i; j++)
                sum += m_HistogramB[j];

            ln = round( 1.0f - ( (sum * 1.0f) / (N * 1.0f) ) );

            newHistogramB[i] = _gMin - round(ln / (alpha * 1.0f));

            if (maxB < newHistogramB[i])
                maxB = newHistogramB[i];
        }

        scaleY = round((float) (maxB * (1.0f) / 200.0f));

        if (scaleY == 0)
            scaleY = 1;

        for (uint i = 0; i < newHistogramB.size(); i++)
        {
            if (newHistogramB[i] == 0)
                continue;

            int idx = 0;
            for (int j = 0; j < newHistogramB[i]; j += scaleY, idx++)
                retImg->setPixel(i, retImg->height() - 1 - idx, histColor);
        }
    }


    return retImg;
}

/*
 alpha = 0.0627
 gMin = 0
*/

QImage *CImageProc::ModifyImgByHistogramGrayScale(const QImage& _img, QImage& _hist, int _gMin, float _alpha)
{
    QImage *retImg = new QImage(_img.width(), _img.height(), m_Data->format());
    std::map<uint, int>  newHistogramR;
    int maxR = 0;
    uint histColor;
    int scaleY = 0;

    // H2:
    int N = _img.width() * _img.height();
    int sum = 0;
    float ln = 0;
    float lnArg = 0.0f;
    uint hix;
    uint colEnd;

    histColor = 0xFF000000;

    InitializeHistogram(newHistogramR);

    for (int i = 0; i < _img.width(); i++)
    {
        for (int j = 0; j < _img.height(); j++)
        {
            uint colPix = (_img.pixel(i, j) >> 16) & 0xFF;
            sum = 0;

            for (uint k = 0; k < colPix; k++)
                sum += m_HistogramR[k];

            lnArg = (sum * 1.0f) / (N * 1.0f);

            if (lnArg >= 1.0f)              // ln(x) x <= 0
                lnArg = 0.9999999f;

            ln =  1.0f - lnArg;

            hix = _gMin - round(log(ln) / (_alpha * 1.0f));
            colEnd = 0xFF << 24;
            colEnd |= (hix << 16);
            colEnd |= (hix << 8);
            colEnd |= hix;

            retImg->setPixel(i, j, colEnd);

            newHistogramR[hix]++;

            if (maxR < newHistogramR[hix])
                maxR = newHistogramR[hix];
        }
    }

    scaleY = round((float) (maxR * (1.0f) / 200.0f));

    if (scaleY == 0)
        scaleY = 1;
/*
    for (uint i = 0; i < newHistogramR.size(); i++)
    {
        if (newHistogramR[i] == 0)
            continue;

        int idx = 0;
        for (int j = 0; j < newHistogramR[i]; j += scaleY, idx++)
            _hist.setPixel(i, _hist.height() - 1 - idx, histColor);
    }
*/
    return retImg;
}

QImage *CImageProc::Splot(const QImage& _img)
{
    m_FilterWindowSize = 3;
    int bordersMin = GetWindowBordersRangeMin();
    int bordersMax = GetWindowBordersRangeMax();

    SetPreviousData();

    for (int i = 0; i < m_Data->width(); i++)
        for (int j = 0; j < m_Data->height(); j++)
        {
            /* Normal case (whole window is in the image)
             *
             * For instance (window 3x3):
             *
             * # # #
             * # # #
             * # # #
             *
             **/
            if  ( ( (i >= bordersMin) && (i <= bordersMax) ) &&
                ( (j >= bordersMin) && (j <= bordersMax) ) )
                {
                    CountSplot(_img, i, j, -bordersMin, -bordersMin);
                }

            /* Particular case: (left column(s) cut off)
             *
             * For instance (window 3x3):
             *
             * # #
             * # #
             * # #
             *
             **/
            else if (( (i < bordersMin) && (i <= bordersMax) ) &&
                     ( (j >= bordersMin) && (j <= bordersMax) ) )
                {
                    CountSplot(_img, i, j, 0, -bordersMin);
                }

            /* Particular case: (right column(s) cut off)
             *
             * For instance (window 3x3):
             *
             * # #
             * # #
             * # #
             *
             **/
            else if (( (i >= bordersMin) && (i > bordersMax) ) &&
                     ( (j >= bordersMin) && (j <= bordersMax) ) )
                {
                    CountSplot(_img, i, j, -bordersMax, -bordersMin);
                }

            /* Particular case: (top row(s) cut off)
             *
             * For instance (window 3x3):
             *
             * # # #
             * # # #
             *
             **/
            else if (( (i >= bordersMin) && (i <= bordersMax) ) &&
                     ( (j < bordersMin) && (j <= bordersMax) ) )
                {
                    CountSplot(_img, i, j, -bordersMin, 0);
                }

            /* Particular case: (bottom row(s) cut off)
             *
             * For instance (window 3x3):
             *
             * # # #
             * # # #
             *
             **/
            else if (( (i >= bordersMin) && (i <= bordersMax) ) &&
                     ( (j >= bordersMin) && (j > bordersMax) ) )
                {
                    CountSplot(_img, i, j, -bordersMin, -bordersMax);
                }
        }

    return m_Data;
}

void CImageProc::CountSplot(const QImage& _orig, int _pixX, int _pixY, int _varK, int _varM)
{
    long long color = 0;
    uint resultCol = 0;

    for (int k = 0; k < m_FilterWindowSize; k++)
        for (int m = 0; m < m_FilterWindowSize; m++)
        {
            color += (long long) (_orig.pixel(_pixX + (k + _varK), _pixY + (m + _varM))) * gMasks[m_SplotMask][k][m];
        }

    if (color < 0)
        color = 0;
    else if (color > 0xFFFFFFFF)
        color = 0xFFFFFFFF;

    resultCol = color;
    resultCol |= 0xFF << 24;
    m_Data->setPixel(_pixX, _pixY, resultCol);
}

QImage* CImageProc::NonLinearFilter(const QImage& _img, int _R)
{
    QImage *retImg = new QImage(_img.width(), _img.height(), m_Data->format());
    long long firstSum = 0;
    long long secondSum = 0;
    int curIdx = 0;
    uint resColorTmp;
    long colLong;
    uint resColor;

    for (int i = 0; i < _img.width(); i++)
        for (int j = 0; j < _img.height(); j++)
        {
            firstSum = 0;
            secondSum = 0;

            for (int k = 1; k <= _R; k++)
            {
                curIdx = i + k - 1;

                if (curIdx >= 0 && curIdx < _img.width())
                    firstSum += _img.pixel(curIdx, j);
                else
                    break;
            }

            for (int k = 1; k <= _R; k++)
            {
                curIdx = i - k;

                if (curIdx >= 0 && curIdx < _img.height())
                    secondSum += _img.pixel(curIdx, j);
                else
                    break;
            }

            if ( (int) firstSum - (int) secondSum < 0)
                colLong = 0;
            else
                colLong = (firstSum - secondSum) / _R;

            if (colLong < 0)
                colLong = 0;

            resColor = colLong;

            if (resColor > 255)
                resColor = 255;

            resColorTmp = 0xFF << 24;
            resColorTmp |= (resColor << 16);
            resColorTmp |= (resColor << 8);
            resColorTmp |= resColor;

            retImg->setPixel(i, j, resColorTmp);
        }

    return retImg;
}

void CImageProc::CreateHighPassFilter(Complex** _fftData, int _w, int _h)
{
    m_Filter = new CFilter(_w, _h, _fftData);
}

int CImageProc::GetImgWidth(){
    return m_Data->width();
}
int CImageProc::GetImgHeight(){
    return m_Data->height();
}
